#include "symbolTable/SymbolTable.h"
#include <memory>

template <typename T>
UniAST::SymbolTable<T>::SymbolTable(Scope *belongTo) : belongTo(belongTo)
{
}

template <typename T> bool UniAST::SymbolTable<T>::exist(std::string name)
{
    return this->symbolTable.contains(name);
}

template <typename T>
std::shared_ptr<T> UniAST::SymbolTable<T>::find(std::string name)
{
    return this->symbolTable[name];
}

template <typename T>
void UniAST::SymbolTable<T>::add(std::string name, std::shared_ptr<T> var)
{
    this->symbolTable.insert(std::make_pair(name, var));
    return;
}

UniAST::Scope::Scope(Scope *parent)
    : variableTable(this), typeTable(this), parent(parent)
{
}

UniAST::Scope *UniAST::Scope::getParentScope()
{
    return this->parent;
}

bool UniAST::Scope::addVariable(std::shared_ptr<Variable> var)
{
    auto varname = var->getName();
    if (this->variableTable.exist(varname))
    {
        return false;
    }
    this->variableTable.add(varname, var);
    return true;
}

bool UniAST::Scope::existVariable(std::string name)
{
    return this->variableTable.exist(name);
}

// return nullptr if var do not exist.
std::shared_ptr<UniAST::Variable>
UniAST::Scope::findVariableFromThis(std::string name)
{
    if (this->variableTable.exist(name))
    {
        return this->variableTable.find(name);
    }
    return nullptr;
}

// return false if failed
bool UniAST::Scope::addType(std::shared_ptr<UniAST::Type> typeSym)
{
    auto tyname = typeSym->getName();
    if (this->typeTable.exist(tyname))
    {
        return false;
    }
    this->typeTable.add(tyname, typeSym);
    return true;
}

// return true if name of type in this scope is `name`
bool UniAST::Scope::existType(std::string name)
{
    return this->typeTable.exist(name);
}

// return nullptr if type do not exist.
std::shared_ptr<UniAST::Type>
UniAST::Scope::findTypeFromThis(std::string name)
{
    if (this->typeTable.exist(name))
    {
        return this->typeTable.find(name);
    }
    return nullptr;
}

UniAST::Manager &UniAST::Manager::getManager()
{
    static Manager manager;
    return manager;
}

UniAST::Manager::Manager()
{
    this->globalScope = std::make_shared<Scope>(nullptr);
    this->scopeStack.push(globalScope);
}

std::shared_ptr<UniAST::Scope> UniAST::Manager::currentScope()
{
    return this->scopeStack.top();
}

void UniAST::Manager::enterScope()
{
    this->scopeStack.push(
        std::make_shared<Scope>(this->currentScope().get()));
}

std::shared_ptr<UniAST::Scope> UniAST::Manager::exitScope()
{
    auto curr = this->currentScope();
    this->scopeStack.pop();
    return curr;
}